package com.antd.common.data.datascope;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import com.antd.common.auth.security.AntdUserDetails;
import com.antd.common.auth.util.AntdSecurityUtil;
import com.antd.common.data.enums.DataScopeTypeEnum;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.sql.Connection;
import java.util.*;
import java.util.stream.Collectors;

/**
 * TODO
 *
 * @author 子丶沫
 * @version 1.0
 * @date 2020/11/18 16:56
 */
@Slf4j
@AllArgsConstructor
@Component
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class DataScopeInterceptor extends AbstractSqlParserHandler implements Interceptor {
    private DataSource dataSource;
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if(log.isDebugEnabled()){
            log.info("进入拦截器");
        }
        StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        // SQL 解析
        this.sqlParser(metaObject);
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        if (!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {
            return invocation.proceed();
        }
        // 针对定义了rowBounds，做为mapper接口方法的参数
        BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
        // 执行的SQL语句
        String originalSql = boundSql.getSql();
        // SQL语句的参数
        Object parameterObject = boundSql.getParameterObject();
        DataScope dataScope = findDataScopeObject(parameterObject);
        if(ObjectUtil.isNull(dataScope)){
            return invocation.proceed();
        }
        String scopeName = dataScope.getScopeName();
        List<String> deptIds = dataScope.getDeptIds();
        if(CollUtil.isEmpty(deptIds)){
            AntdUserDetails user = AntdSecurityUtil.getUser();
            List<String> roleIds = user
                    .getAuthorities()
                    .stream()
                    .map(GrantedAuthority::getAuthority)
                    .filter(authority -> authority.startsWith("ROLE_"))
                    .map(authority -> authority.split("_")[1])
                    .collect(Collectors.toList());
            Optional<Entity> optional = Db.use(dataSource).query("select * from sys_role where role_id IN (" + CollectionUtil.join(roleIds, ",") + ")")
                    .stream().min(Comparator.comparing(o -> o.getInt("ds_type")));
            if(optional.isPresent()){
                Entity entity = optional.get();
                Integer dsType = entity.getInt("ds_type");
                if(dsType==DataScopeTypeEnum.ALL.getType()){
                    return invocation.proceed();
                }
                String dsScope = entity.getStr("ds_scope");
                deptIds.addAll(Arrays.stream(dsScope.split(",")).collect(Collectors.toList()));
                String join = CollectionUtil.join(deptIds, ",");
                originalSql="SELECT * from ("+originalSql+") data_scope where data_scope."+scopeName+"IN ("+join+")";
                metaObject.setValue("delegate.boundSql.sql",originalSql);
                return invocation.proceed();
            }

        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    @Override
    public void setProperties(Properties properties) {

    }
    private DataScope findDataScopeObject(Object obj){
        if(obj instanceof DataScope){
            return (DataScope)obj;
        }else if(obj instanceof Map){
            for(Object map:((Map<?, ?>) obj).values()){
                if(map instanceof DataScope){
                    return (DataScope)map;
                }
            }
        }
        return null;
    }
}
